iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0

前言

本篇文章會完成 JWT 的設定,我們會利用在 User資料表中設定的 UserDetail 來幫忙將用戶資料設定到 JWT 中,生成的辦法會透過 Spring Security 內建的生成方式,Signature 的部分會隨機生成一個來當作加密密鑰,之後便可以檢查 JWT 中是否是使用相同密鑰來快速篩選。

架構

先建立以下的 class

image

ApplicationConfig

這裡我們會建立一些常用的方法讓其他地方可以調用,也就是將常用的功能寫成一個 class,讓需要使用的地方都可以使用

在這裡我們透過 @Bean 來將方法註冊到 Spring 的容器內,讓 Spring 幫忙管理這些方法的生命週期

@Configuration
@RequiredArgsConstructor
public class ApplicationConfig {

    private final UserRepository userRepository;


    @Bean
    public UserDetailsService userDetailsService(){
        //使用userEmail搜尋對應的User資料
        return username -> userRepository.findByEmail(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));
    }

    @Bean
    public AuthenticationProvider authenticationProvider(){
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        //獲取用戶詳情並驗證用戶憑證
        authProvider.setUserDetailsService(userDetailsService());
        //對用戶的密碼進行編碼和匹配
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        //處理身分驗證的主要接口
        return config.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        //將密碼進行加密
        return new BCryptPasswordEncoder();
    }

}

JwtService

在這裡我們要進行 JWT 的生成的工作

首先設定過期時間和金鑰,金鑰的部分可以上網搜尋金鑰產生器,要注意的是如果密鑰太簡單是會報錯的

@Service
public class JwtService {
    //設定過期時間為 1小時
    private static final int EXPIRED_TIME = 60 * 60 * 1000;//過期時間,單位(ms)
    //設定加密密碼
    private static final String SECRET_EKY = "密鑰";

接著我們新增一個方法來幫助我們將用戶傳入的 token 裡面的資料給解析出來

    private Claims extractAllClaims(String token){
        //  獲取這個 token 的資料
        return Jwts
                .parserBuilder()
                .setSigningKey(getSignInKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

然後新增幾個方法來獲取特定資料

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver){
        //  獲取與這個 Token 相關的資料
        final Claims claims = extractAllClaims(token);
        //  傳回 Function 指定的資料,例如:Subject、Expiration
        return claimsResolver.apply(claims);
    }

    private Date extractExpiration(String token) {
        //  回傳這個 Token 的過期時間
        return extractClaim(token, Claims::getExpiration);
    }

    public String extractUserName(String token) {
        //  回傳這個 Token 的主體資料
        return extractClaim(token, Claims::getSubject);
    }

新增一個方法將我們的密鑰編碼成 Base64,並生成 Signature

    private Key getSignInKey() {
        //  生成簽名
        byte[] keyBytes = Decoders.BASE64.decode(SECRET_EKY);
        return Keys.hmacShaKeyFor(keyBytes);
    }

接著新增驗證 Token 的方法,這個方法會使用在 JwtAuthenticationFillter 裡面,用來篩選 JWT 是否有問題

    public boolean isTokenValid(String token, UserDetails userDetails){
        //  檢查Token是否是有效的
        //  先用傳進來的token獲取跟這個token相關的User名稱
        final String userName = extractUserName(token);
        //回傳 User 名稱是否相同,以及 Token 是否過期
        return (userName.equals(userDetails.getUsername())) && !isTokenExpired(token);
    }

    private boolean isTokenExpired(String token) {
        //  檢查這個 Token 的過期時間是不是在現在時間之前
        return extractExpiration(token).before(new Date());
    }

最後生成我們的 JWT,我們會使用 UserDetails 幫我們將資料整理好,我們再直接利用整理好的資料生成 JWT

    public String generateToken(UserDetails userDetails){
        //  外部程式呼叫這個方法並傳入使用者的相關資料
        //  HashMap不是必要的資料,如果要針對傳進來的資料進行一些額外標記的話
        //  就可以在HashMap中放入對應的標記,讓生成JWT資料時將這個標記也加進JWT資料裡面
        return buildToken(new HashMap<>(), userDetails);
    }

    public String buildToken(
            Map<String,Object> extractClaims,
            UserDetails userDetails )
    {
        return Jwts
                .builder()
                //  將特別的標記加進JWT資料
                .setClaims(extractClaims)
                //  將主體資料設定為User的名稱
                .setSubject(userDetails.getUsername())
                //  設定這個資料的建立時間
                .setIssuedAt(new Date(System.currentTimeMillis()))
                //  建立這個資料的過期時間
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRED_TIME))
                //  使用指定的簽名算法和金鑰對 JWT 進行簽名
                .signWith(getSignInKey(), SignatureAlgorithm.HS256)
                //  生成JWT資料
                .compact();
    }

總結

由於後續的設定還很長,因此本篇文章先介紹到這裡,下篇文章會將後半的設定結束並且測試效果


上一篇
[DAY 13] 後端結合身分驗證系統 (3)
下一篇
[DAY 15] 後端結合身分驗證系統 (5)
系列文
智慧語義互動平台:基於Spring和Semantic Kernel的Android應用創新30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言